package cn.cyx.demo.chat.service;

import cn.cyx.demo.chat.model.ChatMessage;
import cn.cyx.demo.chat.model.ChatRoom;
import cn.cyx.demo.chat.model.UserSession;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 聊天服务类 - 聊天室核心业务逻辑处理器
 * 
 * 这个类是整个聊天室系统的核心，负责处理所有的业务逻辑：
 * 1. 用户会话管理：处理用户连接、断开、在线状态维护
 * 2. 聊天房间管理：创建房间、用户加入/离开房间
 * 3. 消息处理：接收、验证、广播聊天消息
 * 4. 数据维护：管理用户会话和房间的数据结构
 * 
 * 技术特点：
 * - 使用ConcurrentHashMap保证线程安全
 * - 采用JSON格式进行消息序列化
 * - 支持实时用户列表更新
 * - 内存存储，适合学习和演示
 * 
 * 设计模式：
 * - 单例模式：每个服务器实例只有一个ChatService
 * - 观察者模式：用户状态变化时通知所有相关用户
 * 
 * @author cyx
 * @since 1.0.0
 */
public class ChatService {

    /**
     * 日志记录器
     * 使用SLF4J框架，便于调试和监控系统运行状态
     */
    private static final Logger logger = LoggerFactory.getLogger(ChatService.class);

    /**
     * JSON对象映射器，用于消息序列化
     * 负责将Java对象转换为JSON字符串，以及反向转换
     * 配置了JavaTimeModule以支持LocalDateTime的序列化
     */
    private final ObjectMapper objectMapper;

    /**
     * 用户会话映射表：用户ID -> 用户会话对象
     * 使用ConcurrentHashMap保证多线程环境下的线程安全
     * 存储所有在线用户的会话信息
     */
    private final Map<String, UserSession> userSessions;

    /**
     * 聊天房间映射表：房间ID -> 聊天房间对象
     * 使用ConcurrentHashMap保证多线程环境下的线程安全
     * 存储所有聊天房间的信息和房间内的用户列表
     */
    private final Map<String, ChatRoom> chatRooms;

    /**
     * 默认房间ID
     * 新用户连接时自动加入的默认聊天房间
     */
    private static final String DEFAULT_ROOM_ID = "default";

    /**
     * 构造函数
     * 
     * 初始化聊天服务，包括：
     * 1. 配置JSON序列化器，支持Java 8时间类型
     * 2. 初始化线程安全的数据结构
     * 3. 创建默认聊天房间
     */
    public ChatService() {
        // 初始化JSON对象映射器
        this.objectMapper = new ObjectMapper();
        // 注册JavaTimeModule以支持LocalDateTime序列化
        // 这样可以将Java 8的时间类型正确序列化为JSON
        this.objectMapper.registerModule(new JavaTimeModule());

        // 初始化线程安全的数据结构
        this.userSessions = new ConcurrentHashMap<>();
        this.chatRooms = new ConcurrentHashMap<>();

        // 创建默认聊天房间
        initializeDefaultRoom();
    }

    /**
     * 初始化默认聊天房间
     * 
     * 这个方法在服务启动时被调用，用于：
     * 1. 检查默认房间是否已存在，避免重复创建
     * 2. 创建默认聊天房间，设置房间信息
     * 3. 记录创建日志，便于调试
     * 
     * 设计考虑：
     * - 使用检查-创建模式，避免并发问题
     * - 记录详细日志，便于问题排查
     */
    private void initializeDefaultRoom() {
        // 检查默认房间是否已存在，避免重复创建
        if (!chatRooms.containsKey(DEFAULT_ROOM_ID)) {
            // 创建默认聊天房间
            ChatRoom defaultRoom = new ChatRoom(DEFAULT_ROOM_ID, "默认聊天室", "system");
            defaultRoom.setDescription("欢迎来到默认聊天室！");
            chatRooms.put(DEFAULT_ROOM_ID, defaultRoom);
            logger.info("默认聊天室已创建: {}", defaultRoom.getRoomName());
        } else {
            logger.debug("默认聊天室已存在，跳过创建");
        }
    }

    /**
     * 处理用户连接
     * 
     * 当新用户连接到聊天室时，这个方法会被调用，完成以下操作：
     * 1. 创建用户会话对象，包含用户信息和网络通道
     * 2. 将用户会话存储到内存中
     * 3. 自动将用户加入默认聊天房间
     * 4. 广播用户加入消息给房间内其他用户
     * 5. 记录连接日志
     * 
     * @param userId   用户唯一标识符
     * @param username 用户名
     * @param channel  Netty网络通道，用于发送消息给该用户
     * @return 创建的用户会话对象
     */
    public UserSession handleUserConnect(String userId, String username, io.netty.channel.Channel channel) {
        // 创建用户会话对象
        UserSession userSession = new UserSession(userId, username, channel);

        // 将用户会话存储到内存中
        userSessions.put(userId, userSession);

        // 自动将用户加入默认聊天房间
        joinRoom(userId, DEFAULT_ROOM_ID);

        // 广播用户加入消息给房间内其他用户
        broadcastUserJoin(userSession);

        // 记录连接日志
        logger.info("用户连接成功: {} ({})", username, userId);
        return userSession;
    }

    /**
     * 处理用户断开连接
     * 
     * 当用户断开连接时，这个方法会被调用，完成以下操作：
     * 1. 从内存中移除用户会话
     * 2. 将用户从当前房间中移除
     * 3. 广播用户离开消息给房间内其他用户
     * 4. 记录断开连接日志
     * 
     * @param userId 要断开连接的用户ID
     */
    public void handleUserDisconnect(String userId) {
        // 从内存中移除用户会话
        UserSession userSession = userSessions.remove(userId);
        if (userSession != null) {
            // 获取用户当前所在的房间ID
            String roomId = userSession.getRoomId();
            if (roomId != null) {
                // 将用户从房间中移除
                leaveRoom(userId, roomId);
            }
            // 广播用户离开消息
            broadcastUserLeave(userSession);
            logger.info("用户断开连接: {} ({})", userSession.getUsername(), userId);
        }
    }

    /**
     * 用户加入房间
     * 
     * 处理用户加入指定房间的逻辑：
     * 1. 验证用户和房间是否存在
     * 2. 如果用户已在其他房间，先离开当前房间
     * 3. 将用户添加到新房间
     * 4. 记录操作日志
     * 
     * @param userId 用户ID
     * @param roomId 要加入的房间ID
     * @return 是否成功加入房间
     */
    public boolean joinRoom(String userId, String roomId) {
        // 获取用户会话和聊天房间
        UserSession userSession = userSessions.get(userId);
        ChatRoom chatRoom = chatRooms.get(roomId);

        // 验证用户和房间是否存在
        if (userSession == null || chatRoom == null) {
            logger.warn("用户或房间不存在: userId={}, roomId={}", userId, roomId);
            return false;
        }

        // 如果用户已在其他房间，先离开当前房间
        String currentRoomId = userSession.getRoomId();
        if (currentRoomId != null && !currentRoomId.equals(roomId)) {
            leaveRoom(userId, currentRoomId);
        }

        // 将用户添加到新房间
        boolean success = chatRoom.addUser(userSession);
        if (success) {
            logger.info("用户加入房间: {} -> {}", userSession.getUsername(), chatRoom.getRoomName());
        } else {
            logger.warn("房间已满，无法加入: {}", chatRoom.getRoomName());
        }

        return success;
    }

    /**
     * 用户离开房间
     * 
     * 处理用户离开指定房间的逻辑：
     * 1. 从房间中移除用户
     * 2. 记录操作日志
     * 
     * @param userId 用户ID
     * @param roomId 要离开的房间ID
     */
    public void leaveRoom(String userId, String roomId) {
        // 获取聊天房间
        ChatRoom chatRoom = chatRooms.get(roomId);
        if (chatRoom != null) {
            // 从房间中移除用户
            UserSession userSession = chatRoom.removeUser(userId);
            if (userSession != null) {
                logger.info("用户离开房间: {} -> {}", userSession.getUsername(), chatRoom.getRoomName());
            }
        }
    }

    /**
     * 处理聊天消息
     * 
     * 当用户发送聊天消息时，这个方法会被调用：
     * 1. 验证用户会话是否存在
     * 2. 验证用户是否在房间中
     * 3. 创建聊天消息对象
     * 4. 广播消息给房间内所有用户
     * 
     * @param userId  发送消息的用户ID
     * @param content 消息内容
     */
    public void handleChatMessage(String userId, String content) {
        // 获取用户会话
        UserSession userSession = userSessions.get(userId);
        if (userSession == null) {
            logger.warn("用户会话不存在: {}", userId);
            return;
        }

        // 获取用户当前所在的房间ID
        String roomId = userSession.getRoomId();
        if (roomId == null) {
            logger.warn("用户未在任何房间中: {}", userId);
            return;
        }

        // 创建聊天消息对象
        ChatMessage chatMessage = new ChatMessage(ChatMessage.MessageType.CHAT, userSession.getUsername(), content);
        chatMessage.setId(UUID.randomUUID().toString());
        chatMessage.setRoomId(roomId);

        // 广播消息给房间内所有用户
        broadcastMessage(roomId, chatMessage);
        logger.debug("收到聊天消息: {} -> {}", userSession.getUsername(), content);
    }

    /**
     * 广播用户加入消息
     * 
     * 当用户加入房间时，向房间内其他用户广播加入消息：
     * 1. 创建用户加入消息
     * 2. 广播消息给房间内所有用户
     * 3. 更新所有用户的在线用户列表
     * 
     * @param userSession 加入的用户会话
     */
    private void broadcastUserJoin(UserSession userSession) {
        // 创建用户加入消息
        ChatMessage joinMessage = new ChatMessage(
                ChatMessage.MessageType.JOIN,
                userSession.getUsername(),
                userSession.getUsername() + " 加入了聊天室");
        joinMessage.setId(UUID.randomUUID().toString());
        joinMessage.setRoomId(userSession.getRoomId());

        // 广播消息给房间内所有用户
        broadcastMessage(userSession.getRoomId(), joinMessage);

        // 广播用户列表更新
        broadcastUserListUpdate();
    }

    /**
     * 广播用户离开消息
     * 
     * 当用户离开房间时，向房间内其他用户广播离开消息：
     * 1. 创建用户离开消息
     * 2. 广播消息给房间内所有用户
     * 3. 更新所有用户的在线用户列表
     * 
     * @param userSession 离开的用户会话
     */
    private void broadcastUserLeave(UserSession userSession) {
        // 获取用户所在的房间ID
        String roomId = userSession.getRoomId();
        if (roomId != null) {
            // 创建用户离开消息
            ChatMessage leaveMessage = new ChatMessage(
                    ChatMessage.MessageType.LEAVE,
                    userSession.getUsername(),
                    userSession.getUsername() + " 离开了聊天室");
            leaveMessage.setId(UUID.randomUUID().toString());
            leaveMessage.setRoomId(roomId);

            // 广播消息给房间内所有用户
            broadcastMessage(roomId, leaveMessage);
        }

        // 广播用户列表更新
        broadcastUserListUpdate();
    }

    /**
     * 广播消息给房间内所有用户
     * 
     * 这是消息广播的核心方法，负责：
     * 1. 验证房间是否存在
     * 2. 将消息序列化为JSON格式
     * 3. 遍历房间内所有在线用户
     * 4. 通过WebSocket发送消息给每个用户
     * 
     * @param roomId  房间ID
     * @param message 要广播的消息对象
     */
    public void broadcastMessage(String roomId, ChatMessage message) {
        // 获取聊天房间
        ChatRoom chatRoom = chatRooms.get(roomId);
        if (chatRoom == null) {
            logger.warn("房间不存在: {}", roomId);
            return;
        }

        try {
            // 将消息序列化为JSON格式
            String jsonMessage = objectMapper.writeValueAsString(message);

            // 遍历房间内所有用户，发送消息
            for (UserSession userSession : chatRoom.getUsers().values()) {
                if (userSession.isOnline()) {
                    userSession.sendMessage(jsonMessage);
                }
            }
            logger.debug("消息广播成功: roomId={}, sender={}", roomId, message.getSender());
        } catch (JsonProcessingException e) {
            logger.error("消息序列化失败", e);
        }
    }

    /**
     * 广播用户列表更新
     * 
     * 当用户加入或离开时，需要更新所有用户的在线用户列表：
     * 1. 获取当前所有在线用户列表
     * 2. 创建用户列表更新消息
     * 3. 将消息序列化为JSON格式
     * 4. 向所有在线用户发送更新消息
     * 
     * 这样前端就能实时显示最新的在线用户列表
     */
    private void broadcastUserListUpdate() {
        try {
            // 获取当前所有在线用户列表
            java.util.List<String> onlineUsers = getOnlineUsers();

            // 创建用户列表更新消息
            Map<String, Object> userListMessage = Map.of(
                    "type", "user_list",
                    "users", onlineUsers);

            // 将消息序列化为JSON格式
            String jsonMessage = objectMapper.writeValueAsString(userListMessage);

            // 向所有在线用户发送用户列表更新
            for (UserSession userSession : userSessions.values()) {
                if (userSession.isOnline()) {
                    userSession.sendMessage(jsonMessage);
                }
            }
            logger.debug("用户列表更新广播成功，在线用户数: {}", onlineUsers.size());
        } catch (JsonProcessingException e) {
            logger.error("用户列表序列化失败", e);
        }
    }

    public UserSession getUserSession(String userId) {
        return userSessions.get(userId);
    }

    public ChatRoom getChatRoom(String roomId) {
        return chatRooms.get(roomId);
    }

    public Map<String, UserSession> getAllUserSessions() {
        return userSessions;
    }

    public Map<String, ChatRoom> getAllChatRooms() {
        return chatRooms;
    }

    public int getOnlineUserCount() {
        return (int) userSessions.values().stream()
                .filter(UserSession::isOnline)
                .count();
    }

    /**
     * 获取在线用户列表
     * 
     * @return 在线用户列表
     */
    public java.util.List<String> getOnlineUsers() {
        return userSessions.values().stream()
                .filter(UserSession::isOnline)
                .map(UserSession::getUsername)
                .sorted()
                .collect(java.util.stream.Collectors.toList());
    }

    /**
     * 获取房间内的在线用户列表
     * 
     * @param roomId 房间ID
     * @return 房间内在线用户列表
     */
    public java.util.List<String> getRoomOnlineUsers(String roomId) {
        ChatRoom chatRoom = chatRooms.get(roomId);
        if (chatRoom == null) {
            return java.util.Collections.emptyList();
        }

        return chatRoom.getUsers().values().stream()
                .filter(UserSession::isOnline)
                .map(UserSession::getUsername)
                .sorted()
                .collect(java.util.stream.Collectors.toList());
    }

    /**
     * 关闭所有用户连接
     * 
     * 这个方法在服务器关闭时被调用，用于：
     * 1. 断开所有用户的WebSocket连接
     * 2. 清理用户会话数据
     * 3. 确保资源正确释放
     */
    public void closeAllConnections() {
        logger.info("正在关闭所有用户连接，当前在线用户数: {}", getOnlineUserCount());

        // 断开所有用户连接
        for (UserSession userSession : userSessions.values()) {
            try {
                if (userSession.isOnline()) {
                    userSession.getChannel().close();
                    logger.debug("已断开用户连接: {}", userSession.getUsername());
                }
            } catch (Exception e) {
                logger.warn("断开用户连接时发生错误: {}", userSession.getUsername(), e);
            }
        }

        // 清空用户会话和房间数据
        userSessions.clear();
        chatRooms.clear();

        logger.info("所有用户连接已关闭，数据已清理");
    }
}